/*
-----------------------------------------------------------------------------
This file is part of libESM.

Copyright (c) 2008 Jacob Essex

libESM is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

libESM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with libESM.  If not, see <http://www.gnu.org/licenses/>.
-----------------------------------------------------------------------------
*/

#ifndef __ESM_H_
#define __ESM_H_

#include <fstream>
#include <list>
#include <set>
#include <string>
#include <cstring>
#include <vector>

#define _CRT_SECURE_NO_DEPRECATE 1

#include "esm_config.h"
#include "esm_types.h"

#ifdef ESM_PY
#ifdef ESM_IOSTREAM
#undef ESM_IOSTREAM
#endif
#endif

//if we use IO stream, we need it
#ifdef ESM_IOSTREAM
#include <iostream>
#endif

#ifdef ESM_BOOST_PTR
#include <boost/shared_ptr.hpp>
#endif


#ifdef ESM_STORE_IN_RECORD_MAP
#define ESM_USE_MAP
#endif

#ifdef ESM_STORE_IN_SUB_MAP
#define ESM_USE_MAP
#endif

#ifdef ESM_USE_MAP
#include <map>
#endif

//boost function or __FUNC__
#ifdef ESM_BOOST_FUNCTION
#include <boost/current_function.hpp>
#define ESM_CUR_FUNC BOOST_CURRENT_FUNCTION
#else
#ifdef __FUNC__
#define ESM_CUR_FUNC __FUNC__
#endif
#endif





#if ESM_CHECKING_LEVEL > 1 //at anything > 1 we throw on ESM_CHECK
#define ESM_CHECK(exp, msg) \
            { if ( !(exp) ){ ESM_EXCEPT(msg); } }
#else //else we assert
#include <cassert>
#define ESM_CHECK(exp, msg) assert(exp && msg)
#endif

#if ESM_CHECKING_LEVEL == 3 //on 3, we count ESM_CHECK_DEBUG as ESM_CHECK
#define ESM_CHECK_DEBUG(exp, msg) ESM_CHECK(exp && msg)
#endif
#if ESM_CHECKING_LEVEL == 2 //else we count it as assert
#include <cassert>
#define ESM_CHECK_DEBUG(exp, msg) assert(exp && msg)
#endif
#if ESM_CHECKING_LEVEL == 1 //else we ignore it
#define ESM_CHECK_DEBUG(exp, msg)
#endif





#if ESM_CHECKING_LEVEL > 1 //if checking is 2 or 3
#ifndef ESM_EXCEPT
#ifdef ESM_CUR_FUNC //ESM_EXCEPT throws
#define ESM_EXCEPT(desc) throw ESM::Exception(desc, ESM_CUR_FUNC, __FILE__, __LINE__ )
#else
#define ESM_EXCEPT(desc) throw ESM::Exception(desc, __FILE__, __LINE__ )
#endif
#endif
#else //else nothing happens
#define ESM_EXCEPT(desc)
#endif


#ifdef ESM_WARNINGS
#define ESM_CHECK_WARNING(exp, msg) ESM_ON_WARNING(exp, msg)
#else
#define ESM_CHECK_WARNING(exp, msg)
#endif





namespace ESM {
    //----------------------------------------------
    //      DEFs/CONSTS
    //----------------------------------------------

    class File;
    class Record;
    class SubRecord;

#ifdef ESM_BOOST_PTR
    typedef boost::shared_ptr<Record> RecordPtr;
    typedef boost::shared_ptr<SubRecord> SubRecordPtr;
#else
    typedef Record* RecordPtr;
    typedef SubRecord* SubRecordPtr;
#endif




    /**
    * @brief the exception class for the libESM
    *
    * It is prefered that the marco ESM_EXCEPT is used rather than calling
    * This directly, as it allows automated passing of strings such as the file, function
    * and other gubbins
    */
    class Exception {
        const std::string mDescription;
        const std::string mFunction;
        const std::string mFile;
        const long mLine;
    public:
        Exception(std::string des, std::string func, std::string file, long line)
                : mDescription(des), mFunction(func), mFile(file), mLine(line) {}
        Exception(std::string des, std::string file, long line)
                : mDescription(des), mFile(file), mLine(line) {}

        /** @return the human written description of the error. A hint about why it occured */
        inline std::string getDescription()const {
            return mDescription;
        }
        /** @return the function in which the exception occured. This may return "" if the function is unknown */
        inline std::string getFunction()   const {
            return mFunction;
        }

        /** @return the file inwhich the exception occured (normally esm.cpp or esm.h) */
        inline std::string getFile()       const {
            return mFile;
        }
        /** @return the line the error occured at */
        inline const long  getLine()       const {
            return mLine;
        }

#ifdef ESM_IOSTREAM
        friend  std::ostream &operator<<(std::ostream &stream, Exception& e);
#endif

    };


#ifdef ESM_PY

    /**
    * @brief a python wrapper for the record class. Shouldn't be used in the C++ version
    */
    class PyRecordList {
        std::vector<RecordPtr> mList;
    public:
        inline void push_back(RecordPtr p) {
            mList.push_back(p);
        }

        RecordPtr getitem(int i) {
            if ( i < 0 )
                i+=(int)mList.size();
            if ( i>=0 && i < (int)mList.size() )
                return mList[i];
            ESM_EXCEPT("Index out of range");
        }

        void setitem(int i, RecordPtr const& v) {
            if ( i < 0 ) i+= (int)mList.size();
            if ( i >= 0 && i < (int)mList.size() ) mList[i]=v;
            else ESM_EXCEPT("Index out of range");
        }
        void delitem(int i) {
            if ( i < 0 ) i += (int)mList.size();
            //if ( i >= 0 && i < (int)mList.size() )
            //    mList.erase(i);
            else ESM_EXCEPT("Index out of range");
        }
        inline void additem(RecordPtr const& v) {
            mList.push_back(v);
        }
        inline std::vector<RecordPtr>::iterator end() {
            return mList.end();
        }
        inline std::vector<RecordPtr>::iterator begin() {
            return mList.begin();
        }
    };
#endif //ESM_PY


    /**
    * @brief converts a 4 byte characther array into a long
    * @par l a char array with a length of 4. Length must be >= 4
    */
    inline long toLong(const char* l){
        return *(long*)l;
    }

    /** @return a length 4 string from the given long.*/
    inline std::string fromLong(long l){
        return std::string((char*)&l, 4);
    }

    /**
    * @brief holds a single sub record as a string
    */
    class SubRecord {
    public:
        /**
        * @param type is the type of the sub record
        * @param data is the data contained within the sub record
        * @param dataLength is the data length
        */
        explicit SubRecord(long type, char* data, long dataLength);
        explicit SubRecord(long type, const std::string& data);
        ~SubRecord();

        /**
        * @brief comparison class. This to allow comparison of ptrs
        */
        bool equals(const SubRecordPtr rhs) const;

        /** @see equals */
        inline bool operator==(const SubRecordPtr rhs) const{
            return equals(rhs);
        }


        /**
        * @brief sets the data within the subrecord
        * @param data The data to be added to the sub record
        */
        inline void setData(const std::string& data) {
            mSubData = data;
            mSubDataLength = (long)data.length();
        }
        inline void setData(const char* data, long length) {
            mSubData = std::string(data, length);
            mSubDataLength = length;
        }


#ifdef ESM_PY
        inline std::string getData() const{
#else
        inline std::string& getData() {
#endif
            return mSubData;
        }

#ifndef ESM_PY
        inline const std::string& getData() const{
            return mSubData;
        }
#endif

        /**
        * @brief aliase of getData
        * @see getData
        */
#ifdef ESM_PY
        inline std::string getString() const{
#else
        inline std::string& getString() {
#endif
            return getData();
        }
#ifndef ESM_PY
        inline const std::string& getString() const{
            return getData();
        }
#endif


        /**
        * @brief gets a copy of a string from the data within the given bounds
        * @par offset the start of the data to get
        * @par length the number of bytes to get
        */
        inline std::string getString(int offset, int length) const {
            ESM_CHECK_DEBUG(offset + length <= mSubDataLength, "Trying to get data beyond the bounds of the data");
            return std::string(mSubData[0]+offset,length);
        }

        /**
        * @brief gets a copy on the long at the given data offset
        * @param offset the offset from the start of the data where the long is located
        * @remarks a long is assumed to be 4 bytes, as it is in the esm format
        */
        inline long getLong(int offset = 0) const {
            ESM_CHECK_DEBUG(offset + 4 <= mSubDataLength, "Trying to get data beyond the bounds of the data");
            long r;
            mSubData.copy((char*)&r,4, offset);
            return r;
        }

        inline float getFloat(int offset = 0) const {
            ESM_CHECK_DEBUG(offset + 4 <= mSubDataLength, "Trying to get data beyond the bounds of the data");
            float r;
            mSubData.copy((char*)&r,4, offset);
            return r;
        }

        /**
        * @brief puts the given block of memory (held in a long) at the offset.
        * @remarks a long is assumed to be 4 bytes, as it is in the esm format
        */
        inline void setLong(long l, int offset = 0) {
            ESM_CHECK_DEBUG(offset + 4 <= mSubDataLength, "Trying to put data beyond the bounds of the data");
            mSubData.replace(offset, 4, ESM::fromLong(l));
        }

        /**
        * @brief converts the string into a null terminated string and stores it
        * @remarks this sets managed to true, as this was created within the app
        */
        inline void setString(const std::string& data) {
            mSubData = data;
            mSubDataLength = (long)data.length();
        }

        /**
        * @brief gets the size of the data stored here
        */
        inline long getDataSize() const {
            ESM_CHECK_DEBUG(mSubDataLength>0, "mSubData should not be less than 0");
            return mSubDataLength;
        }

        ///gets the data type stored in a long. Use fromLong to convert to a readable format
        inline long getType() const{
            return mType;
        }



        template <class T>
        T get() const{ return T(mSubData); }

        template <class T>
        void set(const T& t){ mSubData = t; }

        template <class T>
        T get(int start, int end){ ESM_EXCEPT("Unsuported Type"); return T(); }

        template <class T>
        void set(const T& t, int start, int end){ ESM_EXCEPT("Unsuported Type"); }



#ifdef ESM_IOSTREAM
        friend  std::ostream &operator<<(std::ostream &stream, SubRecord& t);
        friend  std::ostream &operator<<(std::ostream &stream, SubRecordPtr t);
#endif
    private:
        long mType; //ptr to sub name
        std::string mSubData; //sub data.
        long mSubDataLength;
    };
    ///List of sub records
    typedef std::list<SubRecordPtr> SubRecordList;
    ///iterator for a list of sub records
    typedef SubRecordList::iterator SubRecordListItr;
    ///const iterator for a list of records
    typedef SubRecordList::const_iterator SubRecordListCItr;


    template <> inline long SubRecord::get<> (int start, int end){
        return getLong(start);
    }





    /**
    * @brief holds a record, and all the sub records
    */
    class Record {
    public:
        explicit Record(long type);
        /**
        * @brief clean all subrecords
        */
        ~Record();

        /** @see getUniqueSubRecord */
        inline SubRecordPtr operator [](const char* t) {
            return getUniqueSubRecord(t);
        }
        /** @see getUniqueSubRecord */
        inline SubRecordPtr operator [](const std::string& t) {
            return getUniqueSubRecord(t.c_str());
        }
        /** @see getUniqueSubRecord */
        inline SubRecordPtr operator [](const long t) {
            return getUniqueSubRecord(t);
        }

        /** @see equals */
        inline bool operator == (const RecordPtr rhs) const {
            return equals(rhs);
        }
        /**
        * @see equals
        * @todo due to being just !equals, this could do with a speed up
        */
        inline bool operator != (const RecordPtr rhs) const {
            return !equals(rhs);
        }
        /**
        * @brief comparison class
        */
        bool equals(const RecordPtr rhs) const;

        /**
        * @brief checks all the sub records and checks if one exactly the same exists
        */
        bool hasSubRecord(SubRecordPtr s) const;


        /**
        * @return true if a sub record with the given type exists
        */
        bool hasSubRecord(long recordName) const;

        bool hasSubRecord(const char* recordName) const{
            return hasSubRecord(toLong(recordName));
        }

        /**
        * @return the sub record count
        */
        inline int subRecordCount() const {
            return (int)mSubRecordList.size();
        }


        inline SubRecordPtr createSubRecord(const char* type, const std::string& data){
            return createSubRecord(toLong(type), data);
        }
        /**
        * @brief creates a sub record based on the given type using the data from data
        * @par type the data type
        * @par data the data to copy to the sub record
        * @remarks This is simply a wrapper arround the pointer creation, depending on if ESM_BOOST_PTR is used
        */
        inline SubRecordPtr createSubRecord(long type, const std::string& data){
            return _addSubRecord(
#ifdef ESM_BOOST_PTR
                        SubRecordPtr(new SubRecord(type,data))
#else
                        new SubRecord(type,data)
#endif
            );
        }

        /**
        * @brief creates a sub record based on the given type using the data from data
        * @par type the data type
        * @par data the data to copy to the sub record
        * @par length the length of the data arguent
        * @remarks This is simply a wrapper arround the pointer creation, depending on if ESM_BOOST_PTR is used
        */
        inline SubRecordPtr createSubRecord(const char* type, char* data, long length){
            return createSubRecord(toLong(type), data, length);
        }
        /**
        * @brief creates a new sub record
        */
        inline SubRecordPtr createSubRecord(long type, char* data, long length){
            return _addSubRecord(
#ifdef ESM_BOOST_PTR
                        SubRecordPtr(new SubRecord(type,data, length))
#else
                        new SubRecord(type,data, length)
#endif
            );
        }

        /**
        * @brief adds the sub record to the data lists
        *
        * This is not recomened to call manually when not using ESM_BOOST_PTR as it can lead to segfaults on de-init of
        * classes. Instead use createSubRecord
        *
        * The errors would be due to the fact that the class takes on the management of the pointer, if the pointer was added to two
        * classes, they would both try to delete the pointer
        *
        * @return the pointer passed in s. removes the use of temp vars in createSubRecord
        */
        inline SubRecordPtr _addSubRecord(SubRecordPtr s) {
            ESM_CHECK_DEBUG(s, "s should not be 0");
            mSubRecordList.push_back(s);
#ifdef ESM_STORE_IN_SUB_MAP
            mSubRecordMap.insert(std::pair<long,SubRecordPtr>(s->getType(),s));
#endif
            return s;
        }
        /**
        * @see deleteSubRecord
        */
        inline void deleteSubRecord(const char* t) {
            ESM_CHECK_DEBUG(t, "t should not be 0");
            deleteSubRecord(toLong(t));
        }
        /**
        * @brief deletes the sub record! with the type t
        * @remarks node the singualar. It doesn't delete more than one...
        */
        void deleteSubRecord(long t);
        /**
        * @brief deletes sub record defined as S
        * @remarks this deletes EXACTLY s. Done at the ptr level
        */
        void deleteSubRecord(SubRecordPtr s);

        /**
        * @see getUniqueSubRecord
        */
        inline SubRecordPtr getUniqueSubRecord(const char* t) {
            ESM_CHECK_DEBUG(t, "t should not be 0");
            return getUniqueSubRecord(toLong(t));
        }
        /**
        * The subrecord being requested must be unique.
        * That data returned is not garanteed if not (although it will always return a sub record with type t)
        * @return 0 if the sub record cannot be found
        */
        SubRecordPtr getUniqueSubRecord(long t);

        inline SubRecordPtr getSubRecord(long t){
            return getUniqueSubRecord(t);
        }
        inline SubRecordPtr getSubRecord(const char* t){
            return getUniqueSubRecord(t);
        }

        /** @todo remove const from ret val */
        inline const long getType() const {
            return mType;
        }

        ///returns the record header
        inline long getHeader() const {
            return mHeader;
        }
        ///sets the record header
        inline void setHeader(long h) {
            mHeader = h;
        }
        ///gets the record flags
        inline long getFlags() const {
            return mFlags;
        }
        ///sets the record flags
        inline void setFlags(long h) {
            mFlags = h;
        }

        /**
        * @brief gets the start of the SubRecordList list iterator
        * @remarks This allows iterator over the subrecords
        */
        inline SubRecordListItr begin() {
            return mSubRecordList.begin();
        }

        /**
        * @brief gets the end of the SubRecordList list iterator
        * @see begin
        */
        inline SubRecordListItr end() {
            return mSubRecordList.end();
        }

#ifdef ESM_IOSTREAM
        ///allows the use of std::ostream <<
        friend  std::ostream &operator<<(std::ostream &stream, Record& t);
        ///allows the use of std::ostream <<
        friend  std::ostream &operator<<(std::ostream &stream, RecordPtr t);
#endif

    protected:
        SubRecordList mSubRecordList;


#ifdef ESM_STORE_IN_SUB_MAP
        typedef std::map<long, SubRecordPtr> SubRecordMap;
        typedef SubRecordMap::const_iterator SubRecordMapCItr;
        typedef SubRecordMap::const_iterator SubRecordMapItr;
        SubRecordMap mSubRecordMap;
#endif

        long mHeader, mFlags, mType;

    };
    typedef std::list<RecordPtr> RecordList;
    typedef RecordList::iterator RecordListItr;
    typedef RecordList::const_iterator RecordListCItr;
    typedef std::map<std::string, RecordPtr> RecordMap;
    typedef RecordMap::iterator RecordMapItr;
    typedef RecordMap::const_iterator RecordMapCItr;

    /**
    * @brief the base class for handing esm/p/s files
    * This class handles the loading and saving of records
    *
    * To load and save a file, simply use the load and save functions
    * making sure that when you save a file, the header is valid
    * When you load a file, you need to ensure that either you have defined some
    * record types to load using File::addLoadable or have set File::setLoadEverything to true
    *
    * To get records by a key, you need to use the File::setKey functions
    */
    class File {
    public:
        //----------------------------------------------
        //      CON/DESTRUCTORS
        //----------------------------------------------
        /**
        * @par loadEverything. Exactly the same as File::setLoadEverything(true)
        */
        explicit File(bool loadEverything = false);
        ~File();

        //----------------------------------------------
        //      HEADER
        //----------------------------------------------
        /**
        * @brief gets the number of records defined in the header record
        * @remarks this will stay constant, untill you change the header in some way.
        *   Oblviously, if you don't have a header, this fails.
        */
        inline long getOriganalNumberOfRecords() const{
            ESM_CHECK(mHeader, "The header must exist");
            return mHeader->getUniqueSubRecord("HEDR")->getLong(296);
        }

        /**
        * @brief get current num records
        * @return the current number of records from the list of records
        */
        inline long getNumberOfRecords() {
            return (long)mRecordList.size();
        }
        /**
        * @brief returns a pointer to the header
        */
        inline RecordPtr getHeader() const {
            return mHeader;
        }
        /**
        * @brief deletes the old header and adds a new one
        * @par h the header to add. A warning is given if this is the same as getHeader();
        */
        inline void setHeader(RecordPtr h) {
            ESM_CHECK_WARNING(mHeader!=h, "Setting the same header. This will result in a invalid header ptr");
#ifndef ESM_BOOST_PTR
            if ( mHeader )
                delete mHeader;
#endif
            mHeader = h;
        }

        /**
        * @brief setting this to true will result in all record types except the ones in the unloadable list
        * being loaded
        */
        inline void setLoadEverything(bool t) {
            mLoadAllTypes = t;
        }
        inline bool getLoadEverything() const {
            return mLoadAllTypes;
        }
        //----------------------------------------------
        //      LOAD KEYS
        //----------------------------------------------
        /**
        * @brief sets a key for the given type
        * @par t the type to set the key for. E.G. GMST
        * @par k the key to set. E.G. NAME
        * @remarks setting a key for a type before loading a file allows you to retrive
        *   records of that type using the key.
        *   For example, setting a key, GMST, NAME, you could then retrive any loaded GMST
        *   by the value held in the NAME record
        */
        inline void setKey(const char* t, const char* k) {
            setKey(toLong(t), toLong(k));
        }
        inline void setKey(long t, long k) {
            ESM_CHECK(getNumberOfRecords()==0, "Cannot set keys when records exist");
            mLoadableTypes[t] =  k;
        }
        /**
        * @par t the type of the key to get
        * @return the value of a key set with setKey
        * @see setKey
        */
        inline std::string getKey(const char* t) const {
            return getKey(toLong(t));
        }
        inline std::string getKey(long t) const {
            return fromLong(_getKey(t));
        }

        /**
        * @par k the type of the key. E.g. GMST
        * @return true if a key has been set with setKey
        * @see setKey
        */
        inline bool hasKey(const char* k) const {
            return hasKey(toLong(k));
        }

        ///@return true if the record has a key accoidated with it
        inline bool hasKey(long t) const {
            const std::map<long, long>::const_iterator i = mLoadableTypes.find(t);
            return  !( i == mLoadableTypes.end() || i->second == 0  );
        }

        /**
        * @par t the type that has the key. e.g. GMST
        * @brief removes a key
        * @see setKey
        */
        inline void removeKey(const char* t) {
            removeKey(toLong(t));
        }
        inline void removeKey(long t) {
            std::map<long, long>::iterator i = mLoadableTypes.find(t);
            ESM_CHECK(i!=mLoadableTypes.end(), "The given type doesn't exist");
            ESM_CHECK_WARNING(i->second, "The given key doesn't exist");
            i->second = 0;
        }

        //----------------------------------------------
        //      LOAD TYPES
        //----------------------------------------------
        /**
        * Assuming setLoadEverything is false, this allows the load function to load the
        * given type.
        * @par t the 4 byte string to load
        */
        inline void addLoadable(const char* t) {
            addLoadable(toLong(t));
        }
        inline void addLoadable(long t){
            ESM_CHECK_WARNING(mLoadableTypes.find(t) == mLoadableTypes.end(), "Adding an already existing load type. Undefined behavior will result");
            mLoadableTypes.insert(std::pair<long,long>(t,0));
        }

        /** @return true if the given loadable exists */
        inline bool hasLoadable(const char* t) const{
            return hasLoadable(toLong(t));
        }
        inline bool hasLoadable(long t) const{
            return ( mLoadableTypes.find(t) != mLoadableTypes.end() );
        }

        inline void removeLoadable(const char* t) {
            removeLoadable(toLong(t));
        }
        inline void removeLoadable(long t){
            std::map<long, long>::iterator i = mLoadableTypes.find(t);
            ESM_CHECK(i!=mLoadableTypes.end(), "The given type doesn't exist");
            mLoadableTypes.erase(i);
        }

        /**
        * This overrides setLoadEverything to stop the type t being loaded
        * @par t the type not to load, even if setLoadEverything is true
        */
        inline void addNonloadable(const char* t) {
            addNonloadable(toLong(t));
        }
        inline void addNonloadable(long t) {
            ESM_CHECK(mNonloadableTypes.find(t) == mNonloadableTypes.end(), "Adding already existing non load type");
            mNonloadableTypes.insert(t);
        }
        inline bool hasNonloadable(const char* t) const{
            return hasNonloadable(toLong(t));
        }
        inline bool hasNonloadable(long t) const{
            return ( mNonloadableTypes.find(t) != mNonloadableTypes.end() );
        }
        inline void removeNonloadable(const char* t) {
            removeNonloadable(toLong(t));
        }
        inline void removeNonloadable(long t) {
            std::set<long>::iterator i = mNonloadableTypes.find(t);
            ESM_CHECK(i != mNonloadableTypes.end(), "Removing non existing non load type");
            mNonloadableTypes.erase(i);
        }



        //----------------------------------------------
        //      IO
        //----------------------------------------------
        /**
        * @brief loads a file from the disk to the file
        * @par file the file to load. Uses the stl io lib, so anything that works in that works here
        * @par memToAlloc the amout of memory to alloc for reading records (the entire record is read into memory).
        *   If the memory is not large enough, new memory is allocated.
        * @remarks loading two files on top of each other results in undefined behavoir
        */
        inline void load(const std::string& file, unsigned int memToAlloc = 32768) {
            load(file.c_str(), memToAlloc);
        }
        void load(const char* file, unsigned int memToAlloc = 32768 );

        /**
        * @brief saves the current data
        * @remarks for the data to be written correctly, the header must be reasomable valid,
        * However, there is no requirement to have the number of records correct
        */
        inline void save(const std::string& file) const {
            save(file.c_str());
        }
        void save(const char* file) const;


        inline const std::string& getLoadedFile() const{
            return mLoadedFile;
        }

        //----------------------------------------------
        //      RECORD ACCSESSORS
        //----------------------------------------------

        inline const RecordListCItr begin(){
            return mRecordList.begin();
        }
        inline const RecordListCItr end(){
            return mRecordList.end();
        }

#ifdef ESM_STORE_IN_RECORD_MAP
        /**
        * Alias for getRecord
        * @see getRecord
        */
        inline RecordPtr operator [](const std::string& s){
            return getRecord(s);
        }

        /**
        * @brief gets a record stored in the record map
        * @return 0 if the record doesn't exist
        */
        inline RecordPtr getRecord(const std::string& id) {
            RecordMapCItr i = mRecords.find(id);
            if ( i == mRecords.end() )
#ifdef ESM_BOOST_PTR
                return RecordPtr();
#else
                return 0;
#endif
            return i->second;
        }

        inline bool hasRecord(const std::string& id) const {
            RecordMapCItr i = mRecords.find(id);
            return ( i != mRecords.end() );
        }

        inline void deleteRecord(const std::string& id){
            RecordPtr r = getRecord(id);
            if ( r )
                deleteRecord(r);
        }


#endif //ESM_STORE_IN_RECORD_MAP

        /**
        * @brief deletes the record r. The comparison is done by pointer.
        */
        void deleteRecord(RecordPtr r);

        /**
        * @brief creates a record
        */
        inline RecordPtr createRecord(const char* t){
            if ( hasKey(t) )
                ESM_EXCEPT("Cannot create a record with key values defined without the data. Use createRecord(const char*, const std::string&)");

            RecordPtr r = newRecord(t);
            _addRecord(r);
            return r;
        }

        /**
        * @brief when adding a record with a key defined
        * @par t the type of record to create
        * @par keyRecordData the sub record data for the defined key type
        */
        inline RecordPtr createRecord(const char* t, const std::string& keyRecordData){
            if ( !hasKey(t) )
                ESM_EXCEPT("Cannot create a record with no key values defined, use createRecord(const char*)");

            RecordPtr r = newRecord(t);
            r->createSubRecord(_getKey(toLong(t)), keyRecordData );
            _addRecord(r);
            return r;
        }

        /**
        * @brief adds a record to the data lists
        * @par r the record to add
        * @remarks after this has been called, if not using boost::shared_ptr, the class
        *   WILL delete the record on destruction
        */
        inline void _addRecord(RecordPtr r){
            mRecordList.push_back(r);
#ifdef ESM_STORE_IN_RECORD_MAP
            if ( hasKey(r->getType()) )
                mRecords.insert(std::pair<std::string, RecordPtr>(getRecordKey(r->getType(), r), r));
#endif
#ifdef ESM_PY
            mSortedRecords[r->getType()].push_back(r);
#endif
        }

        /**
        * @brief gets all the records with the given type
        * @param t the type of records
        */
#ifdef ESM_PY
        PyRecordList getRecordsByType( long t );
        inline PyRecordList getRecordsByType( const char* t ){
            return getRecordsByType(toLong(t));
        }
#else
        RecordList getRecordsByType( long t );
        inline RecordList getRecordsByType( const char* t ){
            return getRecordsByType(toLong(t));
        }
#endif

    private:

        std::string mLoadedFile;


        /** @brief creates a new record, and returns it. Nothing else */
        inline RecordPtr newRecord(const char* t) const {
            return newRecord(toLong(t));
        }

        inline RecordPtr newRecord(long t) const {
#ifdef ESM_BOOST_PTR
            return RecordPtr(new Record(t));
#else
            return new Record(t);
#endif
        }

        std::map<long, long> mLoadableTypes; //using ptr as key
        std::set<long> mNonloadableTypes;
#ifdef ESM_STORE_IN_RECORD_MAP
        ///holds the records that have a key, by that key
        RecordMap mRecords;
#endif
        ///hold all the records
        RecordList mRecordList;

#ifdef ESM_PY
        std::map<long, PyRecordList> mSortedRecords;
#endif



        ///@return true if the type, t, should be loaded
        inline bool isLoadable(long t) const {
            if ( mNonloadableTypes.find(t) != mNonloadableTypes.end() )
                return false;
            if ( mLoadAllTypes )
                return true;
            return ( mLoadableTypes.find(t) != mLoadableTypes.end() );
        }

        ///gets the key accoiated with this record. Error if they is no key
        inline std::string getRecordKey(long t, RecordPtr r) {
            ESM_CHECK_DEBUG((*r)[_getKey(t)], "Cannot get record accoiated with its key");
            return  (*r)[_getKey(t)]->getString();
        }

        ///gets mLoadableTypes[t]. The type of the key for the type t
        inline long _getKey(long t) const {
            std::map<long, long>::const_iterator i = mLoadableTypes.find(t);

            ESM_CHECK(i!=mLoadableTypes.end(), "The given type hasn't been defined");
            ESM_CHECK(i->second, "The given key doesn't exist");

            return i->second;
        }



        /// creates a new sub record, then reads it, reading all subrecords
        RecordPtr readRecord(std::ifstream& esp, char*& storage, unsigned int& storageLen) const;

        /// @brief reads a sub record, and adds it too the record
        void readSubRecords(std::ifstream& esp, RecordPtr record, long end, char*& storage, unsigned int& storageLen) const;

        ///Writes a record, and all sub records
        void writeRecord(std::ofstream& ofs, RecordPtr r) const;


        /// @return the number of bytes written
        long writeSubRecord(std::ofstream& ofs, SubRecordPtr r) const;

        RecordPtr mHeader; // = 0
        bool mLoadAllTypes; // = false
    };
    typedef boost::shared_ptr<File> FilePtr;
}

#endif //__ESM_H_

